
#ifndef SEM_UTIL__H
#define SEM_UTIL__H

#define NODE_OK 1
#define NODE_ERR 0

#include "dot_util.h"
#include "structures/primitives.h"

#define NODE_OK 1
#define NODE_ERR 0


typage typeRetourFunc = ERR;
int niveau = 0;
int presenceRetour = 0;

/*
    fonction faisant les operations d'un operateur binaire
    op : chaine representant l'operateur a utiliser
    arg_gauche : valeur de retour de la sous expression gauche
    arg_droite : valeur de retour de la sous expression droite
    dot : fichier pour l'affichage en dot
    retourne la valeur de retour $$
    */
node* opbinaire(char* op,node * arg_gauche, node* arg_droite,FILE *dot)
{
    /*gestion de l'arbre*/

    //liaison sous liste
    link_brothers(arg_gauche,arg_droite);

    //creation noeud parent
    node *n= mknode(op, NULL, TOPBINAIRE, NULL,arg_gauche, NULL);

    /*dot*/
    linknode(dot,n,arg_gauche);
    linknode(dot,n,arg_droite);
    createnode(dot,n,op);

    /*entree* e;
        typage t1;
        
        if (verif_operateurBinaire(arg_gauche, arg_droite))
        {
            t1 = getType(arg_gauche->info);
            e = createFakeEntry(t1);
        }
        else
            e = createFakeEntry(ERR);

       n->info = e;*/
    return n;
}
/*
         * function faisant les operations relatives aux array
         * (cree un noeud avec l'id, ajouter l'expression arithmetique
         * comme noeud fils)
         *
         * param :
         *
         * f : fichier dot
         * id : chaine representant l'identifiant de l'array
         * fils : noeud de l'expression arithmetique fille
         *
         * retourne le noeud correspond a l'appel de la variable array
         *
         * */
node * array(FILE* f,char * id,node *fils)
{
    char *s;
    asprintf(&s,"%s%s","array ",id);
    //        printf("array string : %s\n",s);
    node *n = mknode(s,NULL,TARRAY,NULL,fils,NULL);
    createnode(f,n,n->lexeme);
    linknode(f,n,fils);

    entree* e;
    typage t;
    if (verif_array(fils))
    {
        t = getType(fils->info);
        e = createFakeEntry(t);
    }
    else
        e = createFakeEntry(ERR);

    n->info = e;
    return n;
}

int tree_DFS(node *a,symtable *TS_func, symtable *TS_var)
{
    if(a!=NULL)
    {
        //printf("debug tree_DFS %d -> %s",a->type,a->lexeme);printf("\n");
        //cas particulier pour les fonctions et procédure : on enregistre
        //les paramètres dans la table de symbole pour qu'il soit connu par la suite
        if (a->type == TFUNCTION || a->type == TPROCEDURE)
        {
            //printf("debug entrer fonction ou procedure t=%d cpt=%d\n",typeRetourFunc,presenceRetour);
            enregistrer_typeRetour(a);
            enregistrer_paramFunction(a,TS_func,TS_var,niveau);
            niveau++;
            //printf("debug sortie fonction ou procedure t=%d cpt=%d\n",typeRetourFunc,presenceRetour);
        }
        if (a->type == TBLOC)
        {
            enregistrer_varBlock(a,TS_var,niveau);
            niveau++;
        }
        if(tree_DFS(a->enfants,TS_func,TS_var)==NODE_OK)
        {

            if (a->type == TFUNCTION || a->type == TPROCEDURE)
            {
                // on verifie la valeur de retour
                //verif_retour(a);
                if (a->type == TFUNCTION && presenceRetour == 0)
                {
                    printf("ERREUR : pas d'instruction de retour dans la fonction %s \n",a->lexeme);
                }
                //printf("debug raz presence retour \n");
                presenceRetour = 0;

                // on recupere la liste des parametres
                entree* e = getEntry(a->lexeme,TS_func);
                param* params = getParams(e);
                // si il n'y a pas de parametres, on ne depile pas en sortant
                if (params != NULL)
                {
                    popLevel(TS_var);
                    niveau--;
                }
            }

            // dans le cas du bloc, si on a pas fait de déclarations dedans, on ne dépile pas en sortant
            if (a->type == TBLOC && a->infos != NULL)
            {
                popLevel(TS_var);
                niveau--;
            }
            if(tree_DFS(a->frere,TS_func,TS_var)==NODE_OK)
            {
                int verif;
                switch(a->type)
                {
                case TFUNCTION :
                    verif = testRetour(a,a);
                    typeRetourFunc = ERR;
                    enregistrer_registre(a,TS_func);
                    break;
                case TPROCEDURE :
                    verif = testRetour(a,a);
                    typeRetourFunc = ERR;
                    enregistrer_registre(a,TS_func);
                    break;
                case TRETURN : presenceRetour = 1; verif = verif_retour(a); break;
                case TCALLFUN : verif = verif_params(a,TS_func,TS_var); break;
                case TARRAY : verif = verif_array(a); break;
                case TOPBINAIRE : verif = verif_operateurBinaire(a,TS_func); break;
                case TVAR : verif = verif_var(a,TS_var); break;
                default : verif = NODE_OK;
                }
                return verif;
            }
            else return NODE_ERR;
        }
        else return NODE_ERR;
    }
    return NODE_OK;
}

void enregistrer_paramFunction(node* a, symtable *TS_func, symtable *TS_var,int niveau)
{
    // on récupere la liste des paramètres de la fonction appelée
    // à partir de son entrée (que l'on récupere à partir de son lexeme)
    char* funId = a->lexeme;
    entree* e = getEntry(funId,TS_func);
    param* params = getParams(e);

    if (params != NULL)
    {
        char* varId;
        typage t;
        // pour tous les paramètres de la fonction on l'enregistre dans la table de symboles de variables
        while(params->next != NULL)
        {
            varId = params -> lexeme;
            t = params -> type;
            //decoderType(t);
            //ajouter dans la table de symboles des variables
            addDeclaration(niveau,varId,t,NULL,TS_var);
            entree* VarEntry = getEntry(varId,TS_var);
            params->registre = getRegister(VarEntry);
            params = params->next;
        }
        varId = params -> lexeme;
        t = params -> type;
        //decoderType(t);
        //ajouter dans la table de symboles des variables
        addDeclaration(niveau,varId,t,NULL,TS_var);
        entree* VarEntry = getEntry(varId,TS_var);
        params->registre = getRegister(VarEntry);
    }
}

void enregistrer_varBlock(node* a, symtable* TS_var,int niveau)
{
    // on recupere la liste des declarations de variable dans ce bloc
    stringBox* liste = a->infos;

    // si la liste contient des declarations
    if(liste != NULL)
    {
        typage t;
        char* varId;
        while(liste->next != NULL)
        {
            varId = liste->stringId;
            t = liste->type;
            //decoderType(t);
            addDeclaration(niveau,varId,t,NULL,TS_var);
            liste = liste->next;
        }
        varId = liste->stringId;
        t = liste->type;
        //decoderType(t);
        addDeclaration(niveau,varId,t,NULL,TS_var);
    }
}

int verif_params(node* a, symtable *TS_func , symtable *TS_var)
{
    // on récupere l'entrée de la fonction
    char* funId = a->lexeme;
    entree* entreeFunc = getEntry(funId,TS_func);

    // Si l'entree est null c'est que la fonction appellée n'existe pas
    if (entreeFunc != NULL)
    {
        // on récupere la liste des paramètres de la fonction appelée
        param* params = getParams(entreeFunc);

        // on récupere le noeud du premier parametre donné dans l'appel de la fonction
        node* n = a->enfants;

        // pour tous les paramtres
        while(params->next != NULL)
        {
            // Si le parametre donnée n'est pas null
            if (n != NULL)
            {
                // on récupere son type
                entree* e = getEntry(n->lexeme,TS_var);
                typage t = getType(e);

                // Si son type n'est pas le même que celui attendu par
                // le parametre de la fonction correspondante -> OK
                if (params-> type != t)
                {
                    printf("ERREUR : mauvais typage dans les paramètres lors de l'appel de %s !!!\n",a->lexeme);
                    return NODE_ERR;
                }
                // Sinon on continue...

                // on passe au parametre suivant
                params = params->next;
                n = n->frere;
            }
            // Si le parametre donné est null -> ERR
            else
            {
                printf("ERREUR : mauvais typage dans les paramètres lors de l'appel de %s !!!\n",a->lexeme);
                return NODE_ERR;
            }
        }// While

        // Meme traitement pour le dernier parametre
        if (n != NULL)
        {
            entree* e = getEntry(n->lexeme,TS_var);
            typage t = getType(e);

            if (params-> type != t)
            {
               printf("ERREUR : mauvais typage dans les paramètres lors de l'appel de %s !!!\n",a->lexeme);
                return NODE_ERR;
            }
        }
        else
        {
            printf("ERREUR : mauvais typage dans les paramètres lors de l'appel de %s !!!\n",a->lexeme);
            return NODE_ERR;
        }

        //printf("Paramètres OK !!!\n");
        return NODE_OK;
    }

    // Si l'entrée de la fonction est null, elle n'existe pas -> ERR
    else
    {
        printf("ERREUR : la fonction %s n'existe pas !!!\n",funId);
        return NODE_ERR;
    }
}

int verif_retour(node* a)
{
    typage t;
    if(a->enfants != NULL)
    {
        node *child = a->enfants;
        entree* e = child->info;
        t = getType(e);
    }
    else
        t = VOID;
    //printf("debug verifRetour type : "); decoderType(t);printf("\n");
    if (typeRetourFunc == ERR)
    {
        printf("ERREUR : Pas d'instruction de retour dans la fonction\n");
        return NODE_ERR;
    }
    else
    {
        if (t == typeRetourFunc)
        {
            //typeRetourFunc = ERR;
            //printf("Type de retour OK !!!\n");
            return NODE_OK;
        }
        else
        {
            typeRetourFunc = ERR;
            printf("ERREUR : mauvais typage dans le retour de la fonction \n");
            return NODE_ERR;
        }
    }
}

int testRetour(node* fun,node* a)
{
    if(a!=NULL)
    {
        if(a->type == TIF)
        {
            node* child = a->enfants;
            node* nThen = child->frere;
            node* nElse = nThen->frere;
            node* bro = a->frere;
            int thenOk, elseOk,broOk;
            broOk = test(bro,bro);
            thenOk = test(nThen,nThen);
            elseOk = test(nElse,nElse);
            if(broOk == NODE_ERR)
            {
                if(thenOk == elseOk && thenOk==NODE_OK)
                {
                    //printf("OK !!!\n");
                    return NODE_OK;
                }
                else
                {
                    printf("ERREUR : probleme dans les instructions de retour de la fonction %s !!!\n",fun->lexeme);
                    return NODE_ERR;
                }
            }
            else
                return NODE_OK;
        }

        int testRes;
        testRes = testRetour(fun,a->enfants);
        if(a->type != TFUNCTION)
        {
            if(a->frere != NULL && a->frere->type != TMAIN)
                testRes = testRetour(fun,a->frere);
            else
                return NODE_OK;
        }
        else
            return testRes;

    }
}

int test(node* racine, node* courant)
{
    if(courant!=NULL)
    {
        if (courant -> type == TRETURN)
            return NODE_OK;
        else
        {
            if(test(racine,courant->enfants) == NODE_OK)
                return NODE_OK;
            else
                if (courant != racine)
                {
                    if(test(racine,courant->frere) == NODE_OK)
                        return NODE_OK;
                }
            return NODE_ERR;
        }
    }
    else
        return NODE_ERR;
}

void enregistrer_typeRetour(node* a)
{
    // on récupere le type de notre fils
    typage t = getType(a->info);
    // on enregistre son type dans la variable globale typeRetourFunc
    typeRetourFunc = t;
    presenceRetour = 0;
}

int verif_array(node* a)
{
    //Si on a un fils
    if(a->enfants != NULL)
    {
        node *child = a->enfants;
        // on récupere le type de notre fils
        typage t = getType(child->info);

        // on vérifie qu'il est bien de type integer
        if(t == INT)
        {
			//printf("Typage OK !!!\n");
            printf("Erreur de typage!!!\n");
            return NODE_OK;
        }
        else
        {
            printf("Erreur de typage!!!\n");
            return NODE_ERR;
        }
    }

    return NODE_ERR;
}

int verif_operateurBinaire(node* a, symtable* TS_func)
{
    node* arg_gauche = a -> enfants;
    node* arg_droite = arg_gauche -> frere;

    // quand on est sur une variable, on vérifie si elle a bien été déclarée
    if (arg_gauche->type == TVAR)
    {
        entree* e1 = arg_gauche->info;
        if (e1 == NULL)
        {
            printf("ERREUR : VAR %s indefinie \n",arg_gauche->lexeme);
            return NODE_ERR;
        }
    }

    // verif type
    typage t1,t2;
    t1 = getType(arg_gauche->info);
    // quand on est sur un appel de fonction, on récupere son type de retour
    if (arg_droite->type == TCALLFUN)
    {
        entree* e2 = getEntry(arg_droite->lexeme,TS_func);
        t2 = getType(e2);
    }
    else
        t2 = getType(arg_droite->info);
    //decoderType(t1);
    //decoderType(t2);
    entree* e;
    if (t1 == t2)
    {
        //printf("verif ok\n");
        e = createFakeEntry(t1);
        a->info = e;
        return NODE_OK;
    }
    else
    {
        printf("ERREUR : attention erreur de type dans l'utilisation de %s et %s \n",arg_droite->lexeme,arg_gauche->lexeme);
        e = createFakeEntry(ERR);
        a->info = e;
        return NODE_ERR;
    }
}

int verif_var(node* a, symtable* TS_var)
{
    entree* e = getEntry(a->lexeme,TS_var);
    //printf("verif var get entry : %p\n",e);
    if (e != NULL)
    {
        int registreVar = getRegister(e);
        a->infos = (void*) registreVar;
        a->info = e;
        return NODE_OK;
    }
    else
    {
        printf("ERREUR : la variable %s est indefini \n",a->lexeme);
        return NODE_ERR;
    }
}

void enregistrer_registre(node* a, symtable* TS_func)
{
    entree* e = getEntry(a->lexeme,TS_func);

    if (e != NULL)
    {
        int registreFrame = getRegister(e);
        a->infos = (void*) registreFrame;
    }
}

#endif
